#pragma once

#include <istream>
#include <string>
#include <vector>
#include <stack>
#include <list>
#include <map>
#include <set>

#include <boost/optional.hpp>
#include <boost/iostreams/stream.hpp>

#include "../endian.hpp"
#include "../util/verify.hpp"




namespace boostext
{
	namespace iostreams
	{
		using namespace boost::iostreams;

		/**
		 * The class bin_istream is a wrapper around a std::basic_istream.
		 * But instead of providing formatted input, data is read in a binary
		 * manner from the stream.
		 *
		 * The stream can even be instructed to read POD types with another
		 * endian ordering, but will (unless told otherwise) use the current
		 * system's endian ordering.
		 */
		class bin_istream
		{
		public:

			virtual ~bin_istream() {}



			/**
			 * Set the endianess of the stream.
			 */
			virtual void setEndian(endian_ordering ordering) = 0;

			/**
			 * Get the endianess of the stream.
			 */
			virtual endian_ordering endian() const = 0;



			/**
			 * Get the current position of the stream.
			 */
			virtual std::streamsize pos() = 0;

			/**
			 * Get the number of characters, still available in the stream.
			 */
			std::streamsize left()
			{
				return size() - pos();
			}

			/**
			 * Get the total size of the stream.
			 */
			virtual std::streamsize size() = 0;



			/**
			 * Advances the read/write head to pos, where pos
			 * is the relative offset to the start of the stream.
			 */
			virtual void seek(std::streamsize pos) = 0;

			/**
			 * Advances the read/write head by off characters,
			 * where the offset is calculated from:
			 *  - the start of the sequence if way == ios_base::beg
			 *  - the current position if way == ios_base::cur
			 *  - the end of the sequence if way == ios_base::end
			 */
			virtual void seek(stream_offset off, std::ios_base::seekdir way) = 0;



			/**
			 * Delegates reading a number of characters to the actual istream.
			 */
			virtual void read(void *data, std::streamsize n) = 0;

			/**
			 * Gain direct read-only access to a portion of the stream.
			 * The returned pointer, points to the first byte, at the stream's
			 * current position and it's length is no more than n.
			 * Calling this function does not modify the stream's position.
			 *
			 * @throws   boost_ext::ios_failure when the range cannot be satisfied
			 */
			virtual const void* directSequence(std::streamsize n) = 0;



			/**
			* Read a string from this stream.
			*/
			bin_istream& operator>>(std::string& str)
			{
				uint32 size = 0;
				(*this) >> size;

				// Erase the string and make room for at least size bytes
				str.resize(size);
				read(&str[0], size);

				return *this;
			}

			/**
			 * Read a wide string from this stream.
			 */
			bin_istream& operator>>(std::wstring& str)
			{
				uint32 size = 0;
				(*this) >> size;

				// Erase the string and make room for at least size bytes
				str.resize( size );
				read(&str[0], size * sizeof(wchar_t));

				return *this;
			}

			/**
			 * Read a float from this stream.
			 */
			bin_istream& operator>>(float& value)
			{
				read_mind_endian(value);
				return *this;
			}

			/**
			 * Read a double from this stream.
			 */
			bin_istream& operator>>(double& value)
			{
				read_mind_endian(value);
				return *this;
			}

			/**
			 * Read an uint64 from this stream.
			 */
			bin_istream& operator>>(uint64& value)
			{
				read_mind_endian(value);
				return *this;
			}

			/**
			 * Read an int64 from this stream.
			 */
			bin_istream& operator>>(int64& value)
			{
				read_mind_endian(value);
				return *this;
			}

			/**
			 * Read an uint32 from this stream.
			 */
			bin_istream& operator>>(uint32& value)
			{
				read_mind_endian(value);
				return *this;
			}

			/**
			 * Read an int32 from this stream.
			 */
			bin_istream& operator>>(int32& value)
			{
				read_mind_endian(value);
				return *this;
			}

			/**
			 * Read an uint16 from this stream.
			 */
			bin_istream& operator>>(uint16& value)
			{
				read_mind_endian(value);
				return *this;
			}

			/**
			 * Read an int16 from this stream.
			 */
			bin_istream& operator>>(int16& value)
			{
				read_mind_endian(value);
				return *this;
			}

			/**
			 * Read an uint8 from this stream.
			 */
			bin_istream& operator>>(uint8& value)
			{
				read(&value, sizeof(value));
				return *this;
			}

			/**
			 * Read an int8 from this stream.
			 */
			bin_istream& operator>>(int8& value)
			{
				read(&value, sizeof(value));
				return *this;
			}

			/**
			 * Read an bool from this stream.
			 */
			bin_istream& operator>>(bool& value)
			{
				read(&value, sizeof(value));
				return *this;
			}



			/**
			 * Read a std::pair from the istream. The first
			 * member has been serialized in front of the second.
			 * This method requires a streaming operator
			 * for both the key and the value type.
			 */
			template <typename Key,typename Value>
			bin_istream& operator>>(std::pair<Key,Value>& pair)
			{
				(*this) >> pair.first;
				(*this) >> pair.second;

				return *this;
			}

			/**
			 * Read a boost::optional from the istream.
			 * The method will either read the flag, indicating
			 * that the optional is empty, or the value for
			 * the type as well.
			 */
			template <typename T>
			bin_istream& operator>>(boost::optional<T>& opt)
			{
				bool initialized;
				(*this) >> initialized;

				if(initialized)
				{
					T tmp;
					(*this) >> tmp;
					opt = boost::optional<T>(tmp);
				}

				return *this;
			}



			/**
			 * Read a std::vector from the istream.
			 * The size of the vector has been serialized as well as
			 * each element on its own. This method
			 * requires a streaming operator for the
			 * value type.
			 */
			template <typename T,class Ax>
			bin_istream& operator>>(std::vector<T,Ax>& vector)
			{
				// Read the size (number of elements)
				// For 32bit compatibility, every container's size has been serialized as a uint32
				uint32 size;
				(*this) >> size;

				// The following code assumes that there are elements to be added, and would
				// crash if executed with a size of 0. However since there's nothing to do anymore
				// we can quit right here.
				if(!size)
					return *this;

				// When the underlying type employs a trivial assignment operator
				// (or actually is a POD-type), then a memcpy over the entire sequence (size * sizeof(T))
				// is used, to read data into the vector. Otherwise it is filled element
				// by element.
				if(BOOST_HAS_TRIVIAL_ASSIGN(T))
				{
					vector.resize(size);
					read(&vector.front(), size * sizeof(T));
				}
				else
				{
					vector.reserve(size);

					for(std::size_t i = 0; i < size; ++i)
					{
						vector.push_back(T());
						(*this) >> vector.back();
					}
				}

				return *this;
			}

			/**
			 * Read a std::list from the istream.
			 * The size of the list has been serialized as well as
			 * each element on its own.
			 * This method requires a streaming operator for
			 * the value type.
			 */
			template <typename T,class Ax>
			bin_istream& operator>>(std::list<T,Ax>& list)
			{
				// Read the size (number of elements)
				// For 32bit compatibility, every container's size has been serialized as a uint32
				uint32 size;
				(*this) >> size;

				// And then read each element on its own from the ostream
				for(std::size_t i = 0; i < size; ++i)
				{
					list.push_back(T());
					(*this) >> list.back();
				}

				return *this;
			}

			/**
			 * Read a std::map from the istream.
			 * The size of the map has been serialized as well as
			 * each key => value pair.
			 * This method requires a streaming operator for
			 * the key and value type.
			 */
			template <typename Key,typename Value>
			bin_istream& operator>>(std::map<Key,Value>& map)
			{
				// Read the size (number of elements)
				// For 32bit compatibility, every container's size has been serialized as a uint32
				uint32 size;
				(*this) >> size;

				// And then read each element on its own from the istream
				for(std::size_t i = 0; i < size; ++i)
				{
					std::pair<Key,Value> tmp;
					(*this) >> tmp;
					map.insert(tmp);
				}

				return *this;
			}

			/**
			 * Read a std::set from the istream.
			 * The size of the set has been serialized as well as
			 * each element.
			 * This method requires a streaming operator for
			 * the value type.
			 */
			template <typename T,class Ax>
			bin_istream& operator>>(std::set<T,Ax>& set)
			{
				// Read the size (number of elements)
				// For 32bit compatibility, every container's size has been serialized as a uint32
				uint32 size;
				(*this) >> size;

				// And then read each element on its own to the istream
				for(std::size_t i = 0; i < size; ++i)
				{
					T tmp;
					(*this) >> tmp;
					set.insert(tmp);
				}

				return *this;
			}

		private:

			/**
			 * Read a value of an arbitrary type from the ostream, while
			 * paying attention to endian-byte-ordering that is set by the user.
			 * By default, the system's endianness is used.
			 */
			template <typename T>
			void read_mind_endian(T& value)
			{
				endian_ordering ordering = endian();
				if(ordering == little_endian_ordering)
				{
					little_endian<T> tmp;
					read(&tmp, sizeof(tmp));
					value = tmp;
				}
				else if(ordering == big_endian_ordering)
				{
					big_endian<T> tmp;
					read(&tmp, sizeof(tmp));
					value = tmp;
				}
				else
				{
					BOOSTEXT_THROW(invalid_value_exception(ordering));
				}
			}
		};
		///////////////////////////////////////////////////////////////////////////////////////////




		template <typename Device, bool direct /*= true*/>
		class bin_istream_base : public bin_istream
		{
		public:

			typedef typename category_of<Device>::type    mode;
			typedef typename char_type_of<Device>::type   char_type;

		public:

			void read(void* data, std::streamsize n)
			{
				if(n == 0)
					return;

				Device::char_type* start = (*this)->input_sequence().first;
				std::streamsize r = std::min(left(), n);
				if(r != n)
					BOOSTEXT_THROW(ios_failure("Couldn't read requested size"));

				::memcpy(data, &start[pos()], n);

				seek(r, std::ios_base::cur);
			}

			const void* directSequence(std::streamsize n)
			{
				boost::iostreams::stream_offset pos = this->pos();
				boost::iostreams::stream_offset p = pos + n;
				verify_range(p, (stream_offset)0, (stream_offset)size());
				Device::pair_type pair = (*this)->input_sequence();
				return &pair.first[pos];
			}

			std::streamsize size()
			{
				Device::pair_type p = (*this)->input_sequence();
				return p.second - p.first;
			}

			virtual Device& operator*() = 0;
			virtual const Device& operator*() const = 0;
			virtual Device* operator->() = 0;
			virtual const Device* operator->() const = 0;
		};
		///////////////////////////////////////////////////////////////////////////////////////////




		template <typename Device>
		class bin_istream_base<Device,/*bool direct =*/ false> : public bin_istream
		{
		public:

			typedef typename category_of<Device>::type    mode;
			typedef typename char_type_of<Device>::type   char_type;

		public:

			void read(void* data, std::streamsize n)
			{
				if(n == 0)
					return;

				std::streamsize r = (*this)->read(data, n);
				seek(0, std::ios_base::cur);

				if(r != n)
					BOOSTEXT_THROW(ios_failure("Couldn't read requested size"));
			}

			const void* directSequence(std::streamsize n)
			{
				return (*this)->directSequence(n);
			}

			std::streamsize size()
			{
				return (*this)->size();
			}

			virtual Device& operator*() = 0;
			virtual const Device& operator*() const = 0;
			virtual Device* operator->() = 0;
			virtual const Device* operator->() const = 0;
		};
		///////////////////////////////////////////////////////////////////////////////////////////
	}
	///////////////////////////////////////////////////////////////////////////////////////////////
}
///////////////////////////////////////////////////////////////////////////////////////////////////
